package org.apache.maven;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.InvalidRepositoryException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionResult;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.Repository;
import org.apache.maven.model.RepositoryPolicy;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.codehaus.plexus.ContainerConfiguration;
import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.FileUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
import org.eclipse.aether.repository.LocalRepository;

public abstract class AbstractCoreMavenComponentTestCase extends PlexusTestCase {
  @Requirement
  protected RepositorySystem repositorySystem;

  @Requirement
  protected org.apache.maven.project.ProjectBuilder projectBuilder;

  protected void setUp() throws Exception {
    repositorySystem = lookup(RepositorySystem.class);
    projectBuilder = lookup(org.apache.maven.project.ProjectBuilder.class);
  }

  @Override
  protected void tearDown() throws Exception {
    repositorySystem = null;
    projectBuilder = null;
    super.tearDown();
  }

  abstract protected String getProjectsDirectory();

  protected File getProject(String name) throws Exception {
    File source = new File(new File(getBasedir(), getProjectsDirectory()), name);
    File target = new File(new File(getBasedir(), "target"), name);
    FileUtils.copyDirectoryStructureIfModified(source, target);
    return new File(target, "pom.xml");
  }

  /**
   * We need to customize the standard Plexus container with the plugin discovery listener which is
   * what looks for the META-INF/maven/plugin.xml resources that enter the system when a Maven
   * plugin is loaded.
   * 
   * We also need to customize the Plexus container with a standard plugin discovery listener which
   * is the MavenPluginCollector. When a Maven plugin is discovered the MavenPluginCollector
   * collects the plugin descriptors which are found.
   */
  protected void customizeContainerConfiguration(ContainerConfiguration containerConfiguration) {
    containerConfiguration.setAutoWiring(true);
  }

  protected MavenExecutionRequest createMavenExecutionRequest(File pom) throws Exception {
    MavenExecutionRequest request = new DefaultMavenExecutionRequest().setPom(pom).setProjectPresent(true)
        .setShowErrors(true).setPluginGroups(Arrays.asList("org.apache.maven.plugins"))
        .setLocalRepository(getLocalRepository()).setRemoteRepositories(getRemoteRepositories())
        .setPluginArtifactRepositories(getPluginArtifactRepositories()).setGoals(Arrays.asList("package"));

    return request;
  }

  // layer the creation of a project builder configuration with a request, but this will need to be
  // a Maven subclass because we don't want to couple maven to the project builder which we need to
  // separate.
  protected MavenSession createMavenSession(File pom) throws Exception {
    return createMavenSession(pom, new Properties());
  }

  protected MavenSession createMavenSession(File pom, Properties executionProperties) throws Exception {
    MavenExecutionRequest request = createMavenExecutionRequest(pom);

    ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest()
        .setLocalRepository(request.getLocalRepository()).setRemoteRepositories(request.getRemoteRepositories())
        .setPluginArtifactRepositories(request.getPluginArtifactRepositories())
        .setSystemProperties(executionProperties);

    MavenProject project = null;

    if (pom != null) {
      project = projectBuilder.build(pom, configuration).getProject();
    } else {
      project = createStubMavenProject();
      project.setRemoteArtifactRepositories(request.getRemoteRepositories());
      project.setPluginArtifactRepositories(request.getPluginArtifactRepositories());
    }

    initRepoSession(configuration);

    MavenSession session = new MavenSession(getContainer(), configuration.getRepositorySession(), request,
        new DefaultMavenExecutionResult());
    session.setProjects(Arrays.asList(project));

    return session;
  }

  protected void initRepoSession(ProjectBuildingRequest request) throws Exception {
    File localRepoDir = new File(request.getLocalRepository().getBasedir());
    LocalRepository localRepo = new LocalRepository(localRepoDir);
    DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
    session.setLocalRepositoryManager(new SimpleLocalRepositoryManagerFactory().newInstance(session, localRepo));
    request.setRepositorySession(session);
  }

  protected MavenProject createStubMavenProject() {
    Model model = new Model();
    model.setGroupId("org.apache.maven.test");
    model.setArtifactId("maven-test");
    model.setVersion("1.0");
    return new MavenProject(model);
  }

  protected List<ArtifactRepository> getRemoteRepositories() throws InvalidRepositoryException {
    File repoDir = new File(getBasedir(), "src/test/remote-repo").getAbsoluteFile();

    RepositoryPolicy policy = new RepositoryPolicy();
    policy.setEnabled(true);
    policy.setChecksumPolicy("ignore");
    policy.setUpdatePolicy("always");

    Repository repository = new Repository();
    repository.setId(RepositorySystem.DEFAULT_REMOTE_REPO_ID);
    repository.setUrl("file://" + repoDir.toURI().getPath());
    repository.setReleases(policy);
    repository.setSnapshots(policy);

    return Arrays.asList(repositorySystem.buildArtifactRepository(repository));
  }

  protected List<ArtifactRepository> getPluginArtifactRepositories() throws InvalidRepositoryException {
    return getRemoteRepositories();
  }

  protected ArtifactRepository getLocalRepository() throws InvalidRepositoryException {
    File repoDir = new File(getBasedir(), "target/local-repo").getAbsoluteFile();

    return repositorySystem.createLocalRepository(repoDir);
  }

  protected class ProjectBuilder {
    private MavenProject project;

    public ProjectBuilder(MavenProject project) {
      this.project = project;
    }

    public ProjectBuilder(String groupId, String artifactId, String version) {
      Model model = new Model();
      model.setModelVersion("4.0.0");
      model.setGroupId(groupId);
      model.setArtifactId(artifactId);
      model.setVersion(version);
      model.setBuild(new Build());
      project = new MavenProject(model);
    }

    public ProjectBuilder setGroupId(String groupId) {
      project.setGroupId(groupId);
      return this;
    }

    public ProjectBuilder setArtifactId(String artifactId) {
      project.setArtifactId(artifactId);
      return this;
    }

    public ProjectBuilder setVersion(String version) {
      project.setVersion(version);
      return this;
    }

    // Dependencies
    //
    public ProjectBuilder addDependency(String groupId, String artifactId, String version, String scope) {
      return addDependency(groupId, artifactId, version, scope, (Exclusion) null);
    }

    public ProjectBuilder addDependency(String groupId, String artifactId, String version, String scope,
        Exclusion exclusion) {
      return addDependency(groupId, artifactId, version, scope, null, exclusion);
    }

    public ProjectBuilder addDependency(String groupId, String artifactId, String version, String scope,
        String systemPath) {
      return addDependency(groupId, artifactId, version, scope, systemPath, null);
    }

    public ProjectBuilder addDependency(String groupId, String artifactId, String version, String scope,
        String systemPath, Exclusion exclusion) {
      Dependency d = new Dependency();
      d.setGroupId(groupId);
      d.setArtifactId(artifactId);
      d.setVersion(version);
      d.setScope(scope);

      if (systemPath != null && scope.equals(Artifact.SCOPE_SYSTEM)) {
        d.setSystemPath(systemPath);
      }

      if (exclusion != null) {
        d.addExclusion(exclusion);
      }

      project.getDependencies().add(d);

      return this;
    }

    // Plugins
    //
    public ProjectBuilder addPlugin(Plugin plugin) {
      project.getBuildPlugins().add(plugin);
      return this;
    }

    public MavenProject get() {
      return project;
    }
  }

  protected class PluginBuilder {
    private Plugin plugin;

    public PluginBuilder(String groupId, String artifactId, String version) {
      plugin = new Plugin();
      plugin.setGroupId(groupId);
      plugin.setArtifactId(artifactId);
      plugin.setVersion(version);
    }

    // Dependencies
    //
    public PluginBuilder addDependency(String groupId, String artifactId, String version, String scope,
        Exclusion exclusion) {
      return addDependency(groupId, artifactId, version, scope, exclusion);
    }

    public PluginBuilder addDependency(String groupId, String artifactId, String version, String scope,
        String systemPath) {
      return addDependency(groupId, artifactId, version, scope, systemPath, null);
    }

    public PluginBuilder addDependency(String groupId, String artifactId, String version, String scope,
        String systemPath, Exclusion exclusion) {
      Dependency d = new Dependency();
      d.setGroupId(groupId);
      d.setArtifactId(artifactId);
      d.setVersion(version);
      d.setScope(scope);

      if (systemPath != null && scope.equals(Artifact.SCOPE_SYSTEM)) {
        d.setSystemPath(systemPath);
      }

      if (exclusion != null) {
        d.addExclusion(exclusion);
      }

      plugin.getDependencies().add(d);

      return this;
    }

    public Plugin get() {
      return plugin;
    }
  }
}
